﻿using System;
using System.Windows.Automation;
using System.Diagnostics;
using System.Threading;
using System.Collections.Generic;
using System.IO;
using Tedds_Tool.Automatables;
using Compiled_Tests;
using Compiled_Tests.ProcessWrappers;
using Compiled_Tests.Utils;
using Compiled_Tests.AutomationAbstractions;
using CommonInterfaces.Models;
using LEET.LEETCommon;
using LEET.LEETCommon.Model;
using System.Windows;
using LEET.LEETCommon.AutomationAbstractions;
using LEET.LEETCommon.WidgetLookup;
using System.Linq;

namespace Tedds_Tool
{
    public class Recorder
    {
        #region Attributes

        internal List<ITape> tapes;
        private Object lock_target = new Object();
        public delegate void Callback_Delegate(IEvent_Pair ep);
        public Callback_Delegate script_viewer;
        

        public delegate void RecordingEvent();
        public event RecordingEvent RecordingStopped;
        public event RecordingEvent RecordingStarted;
        
        ITapeFactory tapeFactory;

        internal ITape Current;
        Queue<IProcess> toWatch;
        ProcessSpawnWatcher spawnWatcher;

        #endregion

        public delegate IProcess IProcessFactory(Process p);
        IProcessFactory Factory;
        Box Box;
        List<AutomationElement> Boxes = new List<AutomationElement>();
        List<List<AEInfo>> ElementCache = new List<List<AEInfo>>();

        /// <summary>
        /// Recorder allows the automatic generation of test scripts
        /// in TTL format from a user's interactions with the SUT.
        /// </summary>
        public Recorder(IProcessFactory fac, Callback_Delegate cd, ITapeFactory tapeFactory, Box box)
        {
            Box = box;
            Box.Processes = delegate() { return this.Processes; };
            Factory = fac;
            tapes = new List<ITape>();
            script_viewer = cd;
            this.tapeFactory = tapeFactory;

            // so events don't throw null exception, yes it's stupid
            RecordingStarted += delegate() { };
            RecordingStopped += delegate() { };

            toWatch = new Queue<IProcess>();
            spawnWatcher = new ProcessSpawnWatcher(toWatch);
            spawnWatcher.OnSpawn += ProcessSpawned;
            
            Thread t = new Thread(new ThreadStart(spawnWatcher.Run));
            t.Start();
        }


       /// <summary>
        /// Can do this based on an AutomationElement as well.
        /// 
        /// Note: This is the method that Window1 will call into - the others are for testing purposes.
        /// </summary>
        /// <param name="process"></param>
        public void Record(IProcess proc)
        {
            if(tapes.Count == 0)
                RecordingStarted();

            toWatch.Enqueue(proc);
            
            ITape tape = tapeFactory.BuildTape(proc, Callback);
            tapes.Add(tape);
            Callback(tape.Process);
        }

        public bool Child = false;
        private void ProcessSpawned(Process p)
        {
            IProcess proc = Factory(p);
            Child = true;
            Record(proc);
            Child = false;
        }
        /// <summary>
        /// Nulls all nullable variables.
        /// </summary>
        public void Shutdown() 
        {
            spawnWatcher.Dispose();
            foreach (Tape t in tapes)
                if (t != null) t.Dispose();

            tapes.Clear();
            RecordingStopped();
        }

        public int RemoveTape(Process sender)
        {
            ITape t = FindTapeProcess(sender);
            tapes.Remove(t);
            return tapes.Count;
        }

        /// <summary>
        /// Allows Tapes to update master recording, so that the GUI can get updated 
        /// as well.  The use of a lock section should ensure that events are recorded
        /// in event_log in the order in which they occurred.
        /// 
        /// Uh? is that a garentee? Yes
        /// 
        /// </summary>
        /// <param name="ep">
        /// The event the Tape was just recorded.
        /// </param>
        internal void Callback(object sender, object element, IEvent_Pair ep) 
        {
            lock (lock_target)
            {
                if (Current == null || !Current.Process.Equals(sender))
                {
                    ITape tape = FindTape(sender as IProcess);
                    if (tape == null)
                        throw new Exception("Tape Not Found.");
                    Current = tape;
                    Box.RootElement = Current.Process.RootWidget.ae;
                    IEvent_Pair target = new Event_Pair("TARGET | " + (sender as IProcess).Name);
                    script_viewer(target);
                }

                if (script_viewer != null)
                    script_viewer(ep);

                HierarchyFinder.SetUIAHierarchy(Current.Process.RootWidget);
                ElementCache[Boxes.IndexOf(Current.Process.RootWidget.ae)] = HierarchyFinder.AECache;
            }
        }

        /// <summary>
        /// Call back to add new tapes to list of targets
        /// </summary>
        internal void Callback(IProcess sender)
        {
            lock (lock_target)
            {
                if (Current == null || !Current.Process.Equals(sender))
                {
                    ITape tape = FindTape(sender);
                    if (tape == null)
                        throw new Exception("Tape Not Found.");
                    Current = tape;
                    Box.RootElement = Current.Process.RootWidget.ae;
                    Boxes.Add(Box.RootElement);
                    if (!Child)
                    {
                        HierarchyFinder.SetUIAHierarchy(Current.Process.RootWidget);
                        ElementCache.Add(HierarchyFinder.AECache);
                    }
                    IEvent_Pair target = new Event_Pair("TARGET | " + sender.Name);
                    script_viewer(target);
                }
            }
        }

        private ITape FindTapeProcess(Process sender)
        {
            foreach (ITape t in tapes)
            {
                if (t.Process.Process.Equals(sender))
                    return t;
            }
            return null;
        }

        private ITape FindTape(IProcess sender)
        {
            foreach (ITape t in tapes)
            {
                if (t.Process.Equals(sender))
                    return t;
            }
            return null;
        }

        public bool Recording
        {
            get
            {
                for (int i = 0; i < tapes.Count; i++)
                    if (tapes[i].Listening)
                        return true;
                return false;
            }
        }



        public IList<string> Processes { get {
            IList<string> ret = new List<String>();
            foreach (ITape t in tapes)
            {
                ret.Add(t.ProcessName);
            }
            return ret;
        } }

        public IElementState ElementState
        {
            get
            {
                return (Current == null) ? null : Current.State;
            }
        }
    }
}